package com.atao.service.impl;

import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.atao.constants.SystemConstant;
import com.atao.domain.Result;
import com.atao.domain.dto.ArticleExcelDto;
import com.atao.domain.entity.Article;
import com.atao.domain.entity.ArticleTag;
import com.atao.domain.entity.Category;
import com.atao.domain.entity.Tag;
import com.atao.domain.vo.ArticleBackstageHomePageVo;
import com.atao.domain.vo.LocalSearchVo;
import com.atao.domain.vo.TagVo;
import com.atao.mapper.ArticleMapper;
import com.atao.mapper.ArticleTagMapper;
import com.atao.service.*;
import com.atao.utils.Base64ToMultipartFile;
import com.atao.utils.BeanCopyUtils;
import com.atao.utils.DocumentXML;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * (Article)表服务实现类
 *
 * @author makejava
 * @since 2023-02-09 16:07:05
 */
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Resource
    private CategoryService categoryService;
    @Resource
    private TagService tagService;
    @Resource
    private ArticleTagService articleTagService;
    @Resource
    private UploadService uploadService;
    @Resource
    private AuthorService authorService;
    @Resource
    private ArticleTagMapper articleTagMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * Emojis -> EmojisCnt
     */
    private List<Long> parsingEmojiStrings(String emojis) {
        List<Long> ans = new ArrayList<>();
        String[] s = emojis.split(" ");
        for (String value : s) {
            ans.add(Long.valueOf(value));
        }
        return ans;
    }
    /**
     * EmojisCnt -> Emojis 存
     */
    private String parsingEmojiCnt(List<Long> emojiCnt) {
        StringBuilder sb = new StringBuilder();
        for (Long value : emojiCnt) {
            sb.append(value).append(" ");
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }


    /**
     * 为文章装填目录和标签
     */
    public List<Article> loadingCatalogsTags(List<Article> articles) {
        if (articles == null || articles.size() == 0) {
            return articles;
        }
        //设置文章分类名
        for (Article article : articles) {
            String categoryName = ((Category) (categoryService.getCategoryName(article.getArticleCategoryId()).getData())).getName();
            article.setCategoryName(categoryName);
        }
        //获取所有标签
        HashMap<Long, String> tagMap = new HashMap<>();
        tagService.list().forEach(i -> tagMap.put(i.getId(), i.getName()));
        //获取文章标签
        for (Article article : articles) {
            LambdaQueryWrapper<ArticleTag> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(ArticleTag::getArticleId, article.getArticleId());
            List<ArticleTag> tags = articleTagService.list(wrapper);
            List<Long> articleTagsId = tags.stream().map(i -> i.getTagId()).collect(Collectors.toList());
            article.setArticleTagId(articleTagsId);
            article.setTagName(articleTagsId.stream().map(tagMap::get).collect(Collectors.toList()));
            //设置反应
            article.setArticleReactionCnt(parsingEmojiStrings(article.getArticleReaction()));
        }
        return articles;
    }

    /**
     * 获取文章列表
     * @param categoryId 是否查分类
     * @return Result
     */
    @Override
    @Transactional
    public Result articleList(Integer pageNum, Integer pageSize, Long categoryId) {
        List<Article> articles = null;
        String redisKey = SystemConstant.ARTICLE_LIST + pageNum + ":" + pageSize + ":" + categoryId;
        String redisArticlesInfo = redisTemplate.opsForValue().get(redisKey);
        String redisArticlesCntInfo = redisTemplate.opsForValue().get(SystemConstant.ARTICLE_CNT);
        if (redisArticlesInfo != null && redisArticlesCntInfo != null) {
            articles = JSONUtil.toList(redisArticlesInfo, Article.class);
            return Result.ok(articles, Long.valueOf(redisArticlesCntInfo));
        }
        long total;
        //获取所有文章
        if (pageSize == -1) {
            articles = list();
            //总数
            Collections.reverse(articles);
            total = (long) articles.size();
        } else {
            //分页查询
            LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(categoryId != null, Article::getArticleCategoryId, categoryId);
            queryWrapper.orderByDesc(Article::getCreateTime);
            Page<Article> articlePage = new Page<>(pageNum, pageSize);
            page(articlePage, queryWrapper);
            articles = articlePage.getRecords();
            //总数
            total = articlePage.getTotal();
        }
        articles = loadingCatalogsTags(articles);
        redisTemplate.opsForValue().set(redisKey, JSONUtil.toJsonStr(articles));
        redisTemplate.opsForValue().set(SystemConstant.ARTICLE_CNT, String.valueOf(total));
        return Result.ok(articles, total);
    }

    /**
     * 点击编辑文章 、 查看文章
     * @param id
     * @return
     */
    @Override
    @Transactional
    public Result getArticleDetail(Long id) {
        Article article = null;
        String redisKey = SystemConstant.ARTICLE_DETAIL + id;
        String redisArticlesInfo = redisTemplate.opsForValue().get(redisKey);
        if (redisArticlesInfo != null) {
            article = JSONUtil.toBean(redisArticlesInfo, Article.class);
            return Result.ok(article);
        }
        article = getById(id);
        //loadingCatalogsTags
        article = loadingCatalogsTags(Arrays.asList(article)).get(0);
        redisTemplate.opsForValue().set(redisKey, JSONUtil.toJsonStr(article));
        return Result.ok(article);
    }

    @Override
    public Result deleteArticle(List<Long> ids) {
        //删除cnt 文章列表 文章详情
        redisTemplate.delete(SystemConstant.ARTICLE_CNT);
//        redisTemplate.delete(SystemConstant.ARTICLE_LIST);
        Set<String> keys = redisTemplate.keys(SystemConstant.ARTICLE_LIST + "*");
        redisTemplate.delete(keys);
        for (Long id : ids) {
            redisTemplate.delete(SystemConstant.ARTICLE_DETAIL + id);
        }
        this.removeByIds(ids);
        //更新xml
        updateLocalSearchXML();
        return Result.ok();
    }

    /**
     * 写文章--->发送
     * 1.判断图片是否更新，上传oss
     * 2，更新article数据库
     * 3，获取到id
     * 4.获取所有标签名
     * 5.如果标签更新，则删除原来的所有标签，并添加新的
     * @param article 前端传来的文章对象
     */
    @Override
    @Transactional
    public Result addArticle(Article article) {
        //删除cnt 文章列表
        redisTemplate.delete(SystemConstant.ARTICLE_CNT);
        Set<String> keys = redisTemplate.keys(SystemConstant.ARTICLE_LIST + "*");
        redisTemplate.delete(keys);
        redisTemplate.delete(SystemConstant.ARTICLE_DETAIL + article.getArticleId());
        //图片处理
        if (!"".equals(article.getArticleImage()) && !article.getArticleImage().startsWith("http")) {
            String base64data = (article.getArticleImage()).split(";base64,")[1];
            String base64uri = (article.getArticleImage()).split(";base64,")[0];
            MultipartFile file = new Base64ToMultipartFile(base64data, base64uri);
            Result imgResult = uploadService.uploadImg(file);
            if (imgResult.getData() != null) {
                article.setArticleImage(imgResult.getData().toString());
            }
        }
        //如果是新增文章，则设置默认反应
        if (article.getArticleReaction() == null) {
            article.setArticleReaction(SystemConstant.ARTICLE_COMMENT_DEFAULT_EMOJIS);
        }
        saveOrUpdate(article);
        Long articleId = article.getArticleId();
        //获取所有标签
        HashMap<String, Long> tagMap = new HashMap<>();
        tagService.list().forEach(i -> tagMap.put(i.getName(), i.getId()));

        LambdaQueryWrapper<ArticleTag> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ArticleTag::getArticleId, articleId);
        int listedTags = articleTagService.count(wrapper);
        if (listedTags != 0) {
            //删除
            articleTagService.remove(wrapper);
        }
        if (article.getTagName() != null) {
            for (String tag : article.getTagName()) {
                if (tagMap.containsKey(tag)) {
                    articleTagService.save(new ArticleTag(articleId, tagMap.get(tag)));
                } else {
                    Tag newTag = new Tag();
                    newTag.setName(tag);
                    tagService.save(newTag);
                    articleTagService.save(new ArticleTag(articleId, newTag.getId()));
                }
            }
        }
        //更新xml
        updateLocalSearchXML();
        return Result.ok();
    }

    /**
     * 展示特定文章
     * TODO:增加是否置顶，这样就可以显示置顶文章，前台后台都可以用
     * 后台首页图片文章展示，返回特定的VO
     * @return
     * articleId;
     * articleImg;
     * articleTitle;
     */
    @Override
    public Result articleBackstageHomePage(Integer pageNum, Integer pageSize) {
        Page<Article> articlePage = new Page<>(pageNum, pageSize);
        page(articlePage);
        List<ArticleBackstageHomePageVo> homePageVoList = BeanCopyUtils.copyBeanList(articlePage.getRecords(), ArticleBackstageHomePageVo.class);
        for (ArticleBackstageHomePageVo i : homePageVoList) {
            i.setArticleImage("url('" + i.getArticleImage() + "')");
        }
        return Result.ok(homePageVoList);
    }

    /**
     * // 热门搜索-->标签
     */
    @Override
    @Transactional
    public Result articleSearchByTag(Long tagId) {
        List<Article> articles = null;
        String redisKey = SystemConstant.ARTICLE_LIST_TAG + tagId;
        String redisArticlesInfo = redisTemplate.opsForValue().get(redisKey);
        if (redisArticlesInfo != null) {
            articles = JSONUtil.toList(redisArticlesInfo, Article.class);
            return Result.ok(articles);
        }

        LambdaQueryWrapper<ArticleTag> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ArticleTag::getTagId, tagId);
        //标签关联的文章id
        List<Long> articleIdList =
        articleTagService.list(wrapper).stream()
                .map(ArticleTag::getArticleId).collect(Collectors.toList());
        LambdaQueryWrapper<Article> wrapper1 = new LambdaQueryWrapper<>();
        wrapper1.in(Article::getArticleId, articleIdList);
        //查询并加载目录和标签
        articles = loadingCatalogsTags(list(wrapper1));

        redisTemplate.opsForValue().set(redisKey, JSONUtil.toJsonStr(articles));
        return Result.ok(articles);
    }

    /**
     * 后台导出文章列表excel
     * @param response 二进制流
     */
    @Override
    public void exportExcel(HttpServletResponse response) {

        //文件名含中文需要转码
        String fileName =
                System.currentTimeMillis() + ".xlsx";
        try {
            fileName = URLEncoder.encode(System.currentTimeMillis() + ".xlsx", StandardCharsets.UTF_8.toString());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //将需要导出的数据从数据库中查出
        List<Article> articles = (List<Article>) articleList(null, -1, null).getData();
        List<ArticleExcelDto> articlesDto = BeanCopyUtils.copyBeanList(articles, ArticleExcelDto.class);
        for (int i = 0; i < articlesDto.size(); i++) {
            articlesDto.get(i).setTagName(articles.get(i).getTagName().toString());
        }
        //设置响应格式
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        //将OutputStream对象附着到EasyExcel的ExcelWriter实例
        try {
            EasyExcel.write(response.getOutputStream(), ArticleExcelDto.class) //（输出流， 文件头）
                    .excelType(ExcelTypeEnum.XLSX)
                    .autoCloseStream(true)
                    .sheet("文章") //第一个sheet的名
                    .doWrite(articlesDto);
//            EasyExcel.write("C:\\Users\\tlq\\Desktop\\科研\\2\\"+fileName, ArticleExcelDto.class).sheet("模板").doWrite(articlesDto);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 前一篇和后一篇
     * @param id
     * @return ArticleBackstageHomePageVo[before, after]
     */
    @Override
    public Result getBeforeAfterArticles(Long id) {
        ArticleBackstageHomePageVo[] res = null;

        LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
        wrapper.gt(Article::getArticleId, id);
        wrapper.last("limit 1");
        Article after = getOne(wrapper);
        wrapper = new LambdaQueryWrapper<>();
        /*
         * SELECT article
         * FROM article
         * WHERE del_flag=0
         * AND (article_id < ?)
         * ORDER BY article_id DESC
         * limit 1
         */
        wrapper.lt(Article::getArticleId, id).orderByDesc(Article::getArticleId);
        wrapper.last("limit 1");
        Article before = getOne(wrapper);
        ArticleBackstageHomePageVo vo1 = null, vo0 = null;
        if (after != null) {
            vo1 = BeanCopyUtils.copyBean(after, ArticleBackstageHomePageVo.class);
        }
        if (before != null) {
            vo0 = BeanCopyUtils.copyBean(before, ArticleBackstageHomePageVo.class);
        }
        res = new ArticleBackstageHomePageVo[]{vo0, vo1};
        return Result.ok(res);
    }

    /**
     * 更新文章反应
     */
    @Override
    public Result updateReaction(Long articleId, List<Long> cnt) {
        //删除redis
        redisTemplate.delete(SystemConstant.ARTICLE_DETAIL + articleId);

        UpdateWrapper<Article> wrapper = new UpdateWrapper<>();
        wrapper.eq("article_id", articleId);
        wrapper.set("article_reaction", parsingEmojiCnt(cnt));
        update(wrapper);
        return Result.ok();
    }

    /**
     * 查 map -> category - articles
     * 先查article 映射到 simple
     * 根据 articleCategory 映射 map
     * @return
     */
    @Override
    public Result getCategoryArticle() {
        HashMap<String, List<ArticleBackstageHomePageVo>> resMap = null;
        List<Article> articles = null;
        articles = (List<Article>) articleList(-1, -1, null).getData();
//        articles = list();
        List<ArticleBackstageHomePageVo> voList = BeanCopyUtils.copyBeanList(articles, ArticleBackstageHomePageVo.class);
        List<Category> categories = categoryService.list();
        //categoryId - name
        HashMap<Long, String> idMap = new HashMap<>();
        for (Category category : categories) {
            idMap.put(category.getId(), category.getName());
        }
        resMap = new HashMap<>();
        for (int i = 0; i < articles.size(); i++) {
            String categoryName = idMap.get(articles.get(i).getArticleCategoryId());
            List<ArticleBackstageHomePageVo> tempList = resMap.getOrDefault(categoryName, new ArrayList<>());
            tempList.add(voList.get(i));
            resMap.put(categoryName, tempList);
        }
        return Result.ok(resMap);
    }

    /**
     * 标签及标签对应文章数量
     * @return TagVo
     */
    @Override
    public Result getTagArticle() {
        List<TagVo> tagVos = articleTagMapper.getTagArticleBysql();
        HashMap<Long, String> map = new HashMap<>();
        tagService.list().stream().forEach(i -> map.put(i.getId(), i.getName()));
        for (TagVo tagVo : tagVos) {
            tagVo.setTagName(map.get(tagVo.getTagId()));
        }
        return Result.ok(tagVos);
    }

    /**
     * 标签对应文章
     * @param id 标签id
     */
    @Override
    public Result listByTagId(Long id) {
        List<ArticleBackstageHomePageVo> voList = null;
        String redisKey = SystemConstant.ARTICLE_LIST_BY_TAG + id;
        String redisArticlesInfo = redisTemplate.opsForValue().get(redisKey);
        if (redisArticlesInfo != null) {
            voList = JSONUtil.toList(redisArticlesInfo, ArticleBackstageHomePageVo.class);
            return Result.ok(voList);
        }
        LambdaQueryWrapper<ArticleTag> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ArticleTag::getTagId, id);
        List<ArticleTag> articleTags = articleTagService.list(wrapper);
        List<Long> articleId = articleTags.stream().map(i -> i.getArticleId()).collect(Collectors.toList());
        List<Article> articles = listByIds(articleId);
        voList = BeanCopyUtils.copyBeanList(articles, ArticleBackstageHomePageVo.class);
        redisTemplate.opsForValue().set(redisKey, JSONUtil.toJsonStr(voList));
        return Result.ok(voList);
    }

    /**
     * 目录对应文章
     * @param id 目录id
     */
    @Override
    public Result listByCategoryId(Long id) {
        List<ArticleBackstageHomePageVo> voList = null;
        String redisKey = SystemConstant.ARTICLE_LIST_BY_CATEGORY + id;
        String redisArticlesInfo = redisTemplate.opsForValue().get(redisKey);
        if (redisArticlesInfo != null) {
            voList = JSONUtil.toList(redisArticlesInfo, ArticleBackstageHomePageVo.class);
            return Result.ok(voList);
        }
        LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Article::getArticleCategoryId, id);
        List<Article> articles = list(wrapper);
        voList = BeanCopyUtils.copyBeanList(articles, ArticleBackstageHomePageVo.class);
        redisTemplate.opsForValue().set(redisKey, JSONUtil.toJsonStr(voList));
        return  Result.ok(voList);
    }

    /**
     * 更新文章之后更新local-search.xml
     */
    @Override
    public void updateLocalSearchXML() {
        List<Article> articles = null;
//        loadingCatalogsTags(articles);
        articles = (List<Article>) articleList(-1, -1, null).getData();
        List<LocalSearchVo> voList = new ArrayList<>();
        for (int i = 0; i < articles.size(); i++) {
            Article article = articles.get(i);
            LocalSearchVo vo = new LocalSearchVo(article.getArticleTitle(),
                    article.getArticleId().toString(),
                    article.getArticleId().toString(),
                    article.getArticleContent(),
                    Arrays.asList(article.getCategoryName()),
                    article.getTagName());
            voList.add(vo);
        }
//        当前工程根目录
//        String path = this.getClass().getResource("/").getPath() + "templates/local-search.xml";
        File searchFile = null;
        try {
            searchFile = File.createTempFile("local-search", ".xml");
            DocumentXML.createDocument(searchFile, voList);
            uploadService.deleteOss("atao/local-search.xml");
            //上传oss
            uploadService.uploadFile(searchFile);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (searchFile != null) {
                searchFile.deleteOnExit();//程序退出时删除临时文件
            }
        }
    }

}
